创建多对多设计模式
在这里所说的主表就是定义了ManyToManyField的类就是主表
1. 使用 ManyToManyField 创建多对多设计模式
- models.ManyToManyField(to='表的类名', related_name='反向操作使用的属性名')
- ManyToManyField 的参数解释:
- to -> 设置需要关联表的类名
- to='Classes' -> 加上引号是为了在当前models.py文件中通过反射去查询表的类
- to=Classes -> 不加引号,直接使用表的类,如果Classes类定义在to的表类下面那么就会报错,因为Classes未定义就引用了
-> 不加引号的使用场景: 使用导入的表类时无需加引号(不会出现上面的报错情况,因为表类在文件一开始就已经导入了)
- related_name -> 反向操作时,使用的属性名,用于代替原反向查询时的 '表名_set' 的写法 -> (通俗理解: 给主表起的别名,用于反向操作)
- related_query_name -> 反向查询操作时,使用的连接前缀,用于替换表名
- symmetrical -> 仅用于多对多自关联时,指定内部是否创建反向操作的字段,默认为True
- through -> 在使用ManyToManyField字段时,Django将自动生成一张表来管理多对多的关联关系, 但我们也可以手动创建第三张表来管理多对多关系,此时就需要通过through来指定第三张表的表名
- through_fields -> 设置关联的字段,默认使用两张表的id进行关联,through_fields=("author", "book")
- db_table -> 设置第三张表的表名
2.创建多对多的方式一
- 通过 ManyToManyField 自动创建第三张表
- 正常情况下多对多的设计模式需要第三张表,如果使用了 ManyToManyField 去创建多对多设计模式,ManyToManyField 会自动帮你创建第三张表
# models.ForeignKey(to='表的类名', to_field='字段名', related_name='反向操作使用的属性名')
# 班级表
class Classes(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
# 学生表
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
age = models.IntegerField()
classes = models.ForeignKey(to='Classes', related_name='fkclass')
# 教师表
class Teacher(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
classes = models.ManyToManyField(to='Classes') # 通过 ManyToManyField 建立多对多关系
3.创建多对多的方式二
- 方式二又被称之为: 中介模型
- 设置ManyTomanyField并指定自行创建的第三张表
- 使用该方式建立多对多关系的时候,最好将第三张表中的两个关联字段设置为联合唯一索引,因为在正常情况下多对多关系表中的两个关联字段都不可能重复
- 该创建立方式的使用场景:
- 当第三张表需要额外字段的时候
- 例如: 第三张表是相亲表记录表,字段有: 男生id 女生id 相亲时间,这时就需要使用第二种方式创建
# 学生表
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
age = models.IntegerField()
classes = models.ForeignKey(to='Classes', related_name='fkclass')
# 班级表
class Classes(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
# 教师表
class Teacher(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
classes = models.ManyToManyField(to='Classes', through='TeacherToClasses', through_fields=('teacher', 'classes'))
'''
through: 指定第三张表的类名
through_fields: 第一个参数的字段名一定是第三张表中与主表(定义了ManyToManyField的类就是主表)相关的字段名,否则会报错
'''
# 教师和班级的多对多关联表
class TeacherToClasses(models.Model):
id = models.AutoField(primary_key=True)
teacher = models.ForeignKey(to='Teacher')
classes = models.ForeignKey(to='Classes')
class Meta:
# 建立联合唯一索引
unique_together = ("teacher", "classes")
- 注意:
- 使用该方式所建立的多对多关系,ManyToManyField 中的所有方法都会失效(如: add()/create()/set()/clear()/remove()),所以只能直接对第三张表进行数据的增删改操作
- 使用该方式所建立的多对多关系,正向查询 和 反向查询 可以正常使用
# 因为 set() 等方法都无效,只能直接对第三张表进行数据的增删改操作
TeacherToClasses.objects.create(classes_id=1, teacher_id=1)
4.创建多对多的方式三:
- 自行创建第三张表 -> 不推荐使用
- 使用该方式所建立的多对多关系,ManyToManyField 中的所有方法都会失效(如: add()/create()/set()/clear()/remove()),所以只能直接对第三张表进行数据的增删改操作
- 使用该方式所建立的多对多关系,正向查询 和 反向查询 都无法使用
- 使用该方式建立多对多关系的时候,最好将第三张表中的两个关联字段设置为联合唯一索引,因为在正常情况下多对多关系表中的两个关联字段都不可能重复
- 注意: 如果使用了自行创建第三张表的方式,那么在ORM层面作者和书就没有多对多的关系了,就无法直接通过作者找到与他有关的书了
# 学生表
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
age = models.IntegerField()
classes = models.ForeignKey(to='Classes', related_name='fkclass')
# 班级表
class Classes(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
# 教师表
class Teacher(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
# 教师和班级的多对多关联表
class TeacherToClasses(models.Model):
id = models.AutoField(primary_key=True)
teacher = models.ForeignKey(to='Teacher')
classes = models.ForeignKey(to='Classes')
class Meta:
# 建立联合唯一索引
unique_together = ("teacher", "classes")
- 例子: 一个老师可以带多个班级,一个班级可以有多个老师
ManyToManyField字段相关
- ManyToManyField字段相关的和外键字段相关的是一样的(不懂的看回外键章节中的外键字段相关的)
相关方法
1. .create()
- 创建一个新的对象,保存新对象,并将新对象添加到所要关联对象集之中,返回新创建的对象
- 语法: 查询到的对象.类的ManyToManyField属性名.create(字段名='xxx')
# 例子: 通过老师对象添加一个新的班级,并且与该老师进行关联 -> 通俗理解: 添加一个新的班级,并且把该班级与查询到的老师进行关联
teacher = Teacher.objects.get(id=1)
classes_obj = teacher.classes.create(name='七班')
print(classes_obj) # Classes object
2. .add()
- 把查询到的对象或id,添加到与之关联的对象集中 -> 通俗理解: 往多对多的表中添加数据的方法
- 添加对象的写法
- 语法: 查询到的对象.类的ManyToManyField属性名.add(对象, 对象, ……)
# 例子: 将多个班级与指定的老师建立关联
class_lis = Classes.objects.filter(id__lt=3)
Teacher.objects.get(id=1).classes.add(*class_lis)
- 添加id的写法
- 语法: 查询到的对象.类的ManyToManyField属性名.add(id, id, ……)
# 例子: 将多个班级与指定的老师建立关联
Teacher.objects.get(id=1).classes.add(*[1, 2])
# 等同于
Teacher.objects.get(id=1).classes.add(1, 2)
3. .set()
- 修改多对多的表中数据的方法
- 注意:
- 在使用完 .set() 方法后,一定要执行 .save()
- .set() 方法自带了 .add() 方法的功能,所以可以把 .set() 当做 .add() 使用,但是所要传递的参数是有区别的
- .set() 方法会先清除原数据,然后再添加新的数据
- 添加对象的写法
- 语法: 查询到的对象.类的ManyToManyField属性名.set([对象, 对象, ……]) -> 接收一个可迭代对象
# 例子: 修改id为1的教师,所带的班级id为 (1, 3, 5) -> 原来带的班级是(6, 7, 8)
class_list = Classes.objects.filter(id__in=[1, 3, 5])
teacher = Teacher.objects.get(id=1)
teacher.classes.set(class_list)
teacher.save()
- 添加id的写法
- 语法: 查询到的对象.类的ManyToManyField属性名.set([1, 2, 3]) -> 接收一个可迭代对象
# 例子: 修改id为1的教师,所带的班级id为 (1, 3, 5) -> 原来带的班级是(6, 7, 8)
teacher = Teacher.objects.get(id=1)
teacher.classes.set([1, 3, 5])
teacher.save()
4. .remove()
- 删除多对多的表中数据的方法
- 所传入的参数是对象的写法
- 语法: 查询到的对象.类的ManyToManyField属性名.remove(对象, 对象, ……)
# 删除多对多表中 teacher_id=1 且 classes_id=2 和 classes_id=4 的数据
class_list = Classes.objects.filter(id__in=[2, 4])
Teacher.objects.get(id=1).classes.remove(*class_list)
- 所传入的参数是id的写法
- 语法: 查询到的对象.类的ManyToManyField属性名.remove(1, 2, 3)
# 删除多对多表中 teacher_id=1 且 classes_id=2 和 classes_id=4 的数据
Teacher.objects.get(id=1).classes.remove(2, 4)
5. .clear()
- 清空指定对象在多对多表中的所有数据
- 语法: 查询到的对象.类的ManyToManyField属性名.clear()
Teacher.objects.get(id=1).classes.clear()
6. .remove() 和 .clear() 的注意事项
- 对于ForeignKey对象,当 ForeignKey 设置为 null=True 时 clear()和remove()方法才能使用
- 当 ForeignKey 字段没设置 null=True 时
# models.py
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
classes = models.ForeignKey(to='Classes')
# views.py
classes_obj.student_set.clear()
# 报错提示
Traceback (most recent call last):
File "C:/Users/Mr. Yeung/Desktop/PyFolder/SMS/test_orm.py", line 12, in <module>
classes_obj.student_set.clear()
AttributeError: 'RelatedManager' object has no attribute 'clear'
- 当 ForeignKey 字段设置了 null=True 时
# models.py
class Student(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
classes = models.ForeignKey(to='Classes', null=True)
# views.py
classes_obj.student_set.clear()
查询
1. 正向查询
- 正向查询用字段,反向查询用表名
- 写法一:
- 通过对象查找
- 语法: 查询到的对象.类的ManyToManyField属性名.all/filter/get() -> 返回值:QuerySet
classes_list = Teacher.objects.get(id=1).classes.all() # <QuerySet [<Classes: Classes object>, <Classes: Classes object>, <Classes: Classes object>]>
for classes in classes_list:
print(classes.id, classes.name) # 1 一班
- 写法二:
- 使用 values 或 values_list 通过字段查询
- 语法:
- .values('主表字段名', '主表字段名', '类的ManyToManyField属性名__从表字段名', '类的ManyToManyField属性名__类的ManyToManyField属性名下的ManyToManyField属性名__从表字段名')
- .values_list('主表字段名', '主表字段名', '类的ManyToManyField属性名__从表字段名', '类的ManyToManyField属性名__类的ManyToManyField属性名下的ManyToManyField属性名__从表字段名')
data1 = Teacher.objects.values('name', 'classes__id', 'classes__name')
print(data1) # <QuerySet [{'classes__name': '一班', 'name': '李老师', 'classes__id': 1}, {'classes__name': '二班', 'name': '李老师', 'classes__id': 2}, ……]>
data2 = Teacher.objects.values_list('name', 'classes__id', 'classes__name')
print(data2) # <QuerySet [('李老师', 1, '一班'), ('李老师', 2, '二班'), ('李老师', 3, '三班'), ('黄老师', 2, '二班'), ('黄老师', 3, '三班'), ('杨老师', 1, '一班')]>
- 跨多张表进行查询 ->查找书名是“西游记”的书的作者的年龄和手机号码和作者详情id(是ORM练习中的最后一题)
author_list = Book.objects.filter(title='西游记').values('author__name', 'author__detail_id', 'author__detail__addr', 'author__detail__email')
2. 反向查询
- 正向查询用字段,反向查询用表名
- 写法一:
- 通过对象查找
- 语法: 查询到的对象.主表的类名_set.all/get/filter() -> 主表类名首字母无需大写
# 例子: 查询一班的所有学生
teacher_list = Classes.objects.get(id=1).teacher_set.all() # <QuerySet [<Teacher: Teacher object>, <Teacher: Teacher object>]>
for teacher in teacher_list:
print(teacher.id, teacher.name) # 1 李老师
- 设置了 related_name='xxx'
# models.py
class Teacher(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
classes = models.ManyToManyField(to='Classes', related_name='mfclass')
# views.py
teacher_list = Classes.objects.get(id=1).mfclass.all() # <QuerySet [<Teacher: Teacher object>, <Teacher: Teacher object>]>
for teacher in teacher_list:
print(teacher.id, teacher.name) # 1 李老师
- 写法二:
- 使用 values 或 values_list 通过字段查询
- 语法:
- 如果没有设置表类中外键属性的 related_name
- .values('从表字段名', '从表字段名', '主表类名__主表字段名', '主表类名__主表字段名') -> 主表类名无需大写
- .values_list('从表字段名', '从表字段名', '主表类名__主表字段名', '主表类名__主表字段名') -> 主表类名无需大写
teacher_list1 = Classes.objects.all().values('name', 'teacher__id', 'teacher__name')
print(teacher_list1) # < QuerySet[{'teacher__name': '李老师', 'teacher__id': 1, 'name': '一班'}, {'teacher__name': '李老师', 'teacher__id': 1, 'name': '二班'}, ……] >
teacher_list2 = Classes.objects.all().values_list('name', 'teacher__id', 'teacher__name')
print(teacher_list2) # < QuerySet[('一班', 1, '李老师'), ('二班', 1, '李老师'), ('三班', 1, '李老师'), ('二班', 2, '黄老师'), ('三班', 2, '黄老师'), ('一班', 3, '杨老师'), ('四班', None, None)] >
- 如果设置了表类中外键属性的 related_name
- .values('从表字段名', '从表字段名', 'related_name所设置的名字__主表字段名', 'related_name所设置的名字__主表字段名')
- .values_list('从表字段名', '从表字段名', 'related_name所设置的名字__主表字段名', 'related_name所设置的名字__主表字段名')
# models.py
class Teacher(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=10)
classes = models.ManyToManyField(to='Classes', related_name='mfclass')
# views.py
teacher_list1 = Classes.objects.all().values('name', 'mfclass__id', 'mfclass__name')
print(teacher_list1) # <QuerySet [{'mfclass__name': '李老师', 'mfclass__id': 1, 'name': '一班'}, {'mfclass__name': '李老师', 'mfclass__id': 1, 'name': '二班'}, ……]>
teacher_list2 = Classes.objects.all().values_list('name', 'mfclass__id', 'mfclass__name')
print(teacher_list2) # <QuerySet [('一班', 1, '李老师'), ('二班', 1, '李老师'), ('三班', 1, '李老师'), ('二班', 2, '黄老师'), ('三班', 2, '黄老师'), ('一班', 3, '杨老师'), ('四班', None, None)]>
删除数据的注意事项
1. 如果Teacher表和Classes表创建了多对多关系后生成了第三张表,当Teacher表中有一条数据被删除了,那么多对多关系表中与该条数据相关的所有数据都会被删除